Revisiting Clean Code as a Senior Engineer – Part 2

Thomas / June 12, 2024 in 

In my last post, I discussed BTI360’s Clean Code 101 course, the importance of Clean Code, and three of my main takeaways from revisiting Clean Code as a Senior Engineer. Today I will conclude with three more topics. If you have not read my last post, I would recommend doing so, since today’s topics will build upon the first three.

4. Comments

Many of us had the importance of commenting our code drilled into us in introductory programming courses: give each of your classes a block comment; give each of your functions a block comment; give any complicated line of code a comment. But any software engineer who understands Clean Code will tell you that most of the comments you think you need are actually completely unnecessary.

So what is wrong with comments? First, comments are not compilable, executable, or testable, and are therefore untrustworthy. Second, comments grow stale quickly: code is frequently refactored, but it is easy to forget to update your comments, especially since our IDEs do not underline them for us when they are wrong. I bet every experienced engineer has encountered stale, misleading, or downright wrong comments. As Uncle Bob says, a bad comment is worse than no comment at all.

More importantly, comments are often used in an attempt to repair “unclean” code, which is not otherwise readable or understandable. Many comments could simply be eliminated by Clean Code practices such as naming things effectively, keeping functions small, extracting similar code together into a class, or writing useful automated tests. You do not need to list the class attributes or function arguments in a block comment—they are already listed in the code itself. And describing the expected use cases is not required if you have tests which demonstrate those use cases.

Of course, as with most Clean Code practices, there are exceptions. I have used comments to summarize complex mathematical logic that my code is performing or include links to relevant articles. I have used tools that automatically generate user documentation from block comments on public functions. But those are exceptions. Looking back, I believe I could have been much better at writing fewer comments and more Clean Code.

Takeaway: Question every comment that you write. If you used better names, added new test cases, or extracted the code into small, well-named functions or classes, would you still need the comment? When possible, renaming, refactoring, and automated testing are almost always better than adding comments.

5. The Law of Demeter

The “Law of Demeter” is a guideline stating that the code inside of a class’s function should only invoke functions on the class itself, the class’s attributes, the function’s arguments, objects instantiated within the function, or global objects. Notably, you should not invoke functions or attributes on objects returned by other functions. To put it simply, “use only one dot,” avoiding statements like “a.b().c” (invoking “c” on the object returned by invoking function “b”). The goal here is to simplify your code and prevent tight coupling.

However, this “law” has also been criticized for its shortcomings. First, you can usually “fix” your code by just writing a wrapper function whenever you would need to use multiple “dots”, thereby avoiding the extra “dot” completely. This is obviously silly. Second, there are circumstances in which using multiple “dots” is expected and perfectly reasonable, such as when “a” is a simple data structure made for storing objects like “b,” or when utilizing a Builder pattern.

But I encourage you to take a step back and consider the spirit of the law. When you encounter a statement like “a.b().c” you should really be thinking about whether you need to refactor your code. Perhaps “b” should return “c” itself. Maybe “a” should have access to “c” directly. What if you need to move function “b” into the current class? Or you might even need to move the current function out of its class and into “a”! The Law of Demeter is simply another tool that, while not perfect, can tell us when we might need to “clean up” our code.

Takeaway: Whenever you encounter a statement containing more than one “dot,” it might be an indication that the code is tightly coupled and should be refactored. Investing some time now to rethink your design and clean up your code can prevent complexity from growing as the codebase expands.

6. Command Query Separation

Command-query separation (CQS) is the practice of writing each function as either a “command,” which can change state but cannot return data, or a “query,” which returns data but cannot change state. By separating “commands” from “queries,” it is clear which functions have side effects, making your code more maintainable—a.k.a., “cleaner.”

This is far from common practice. You can find plenty of popular libraries that violate CQS. You have probably written plenty of useful functions that violate CQS yourself. It seems convenient in many circumstances. Perhaps you want to perform an action that changes state and then return the resulting data, or return a boolean success status. That totally makes sense. So what is the big deal?

It becomes more obvious when you discover unexpected side effects from functions you assumed were “queries.” For example, consider the JavaScript Array sort function: it both sorts an array in place and returns the sorted array. If you are not very familiar with JavaScript, or frequently switch between languages, it is easy to forget this nuance. You see that an array is returned and assume the original input array will remain unmodified. I have encountered this type of issue enough times to understand what makes CQS so appealing.

OK, so maybe I am starting to convince you about the merits of CQS. But it seems annoying, right? Yes, CQS is admittedly a difficult paradigm shift to undertake, but you can use Clean Code to help you out. Keeping your functions small makes it easier to separate the “commands” from the “queries.” Extracting related functions into classes allows your classes to manage chains of “commands” and “queries” internally (handling the intermediary variables) and provide a simple public API on top of it. Following the Law of Demeter to ensure your code stays loosely coupled leads to architecture that is simpler and more capable of supporting CQS.

For example, consider a class that manages interactions between your application and a database. Properly following CQS would require writing two separate functions for each interaction (e.g. running a search query): one that performs the action (the “command”), and one that returns the results from that action (the “query”). This means you would need to store the results from the “command” within the class state, but it also means you could then access the results via the “query” multiple times without repeating the same action. Furthermore, it makes each function easier to mock and test.

Takeaway: Avoid writing functions that have “side effects” and return data. Separating your “command” from your “query” will make it easier to understand, follow, and debug your code.

Conclusion

At BTI360, we have a saying: Grow Each Day. As one of our “Core Behaviors,” we believe that continuous growth is one of the most important responsibilities of our careers. That is part of the reason why we put so much emphasis on courses like Clean Code 101.

The Clean Code course emphasized to me how there is always so much more to learn. If I had entered the course believing I already knew everything there is to know about Clean Code, then I would have learned nothing. I would have walked away without absorbing all of these valuable lessons, and I, my team, and our customers would be worse off for it. Instead, I approached it with an open mind; at first, cautiously optimistic, but by the end of the course, excitedly. Now, I am, well, not a Clean Code “master,” but perhaps you could say I am well on the path to mastering Clean Code. It is a journey that never ends.

Bonus Takeaway: Grow each day. Seek out new opportunities to learn, and approach them with enthusiasm.


Career Opportunities
Are you looking to join a software company that invests in its teammates and promotes a strong engineering culture? Check out our current Career Opportunities. We’re always looking for like-minded engineers to join the BTI360 team.

Previous

Revisiting Clean Code as a Senior Engineer – Part 1

Next

BTI360 Generative AI Solutions Listed in AWS “ICMP” for the US Federal Government

Close Form

Enjoy our Blog?

Then stay up-to-date with our latest posts delivered right to your inbox.

  • This field is for validation purposes and should be left unchanged.

Or catch us on social media

Stay in Touch

Whether we’re honing our craft, hanging out with our team, or volunteering in the community, we invite you to keep tabs on us.

  • This field is for validation purposes and should be left unchanged.